package com.miku.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.miku.annotation.AuthCheck;
import com.miku.common.BaseResponse;
import com.miku.common.DeleteRequest;
import com.miku.common.ErrorCode;
import com.miku.common.ResultUtils;
import com.miku.constant.AiConstant;
import com.miku.constant.UserConstant;
import com.miku.exception.BusinessException;
import com.miku.exception.ThrowUtils;
import com.miku.manager.RedisLimterManager;
import com.miku.model.dto.chart.*;
import com.miku.model.entity.Chart;
import com.miku.model.entity.User;
import com.miku.model.enums.ChartStatusEnum;
import com.miku.model.enums.FileUploadBizEnum;
import com.miku.mq.producer.MessageProducer;
import com.miku.service.ChartService;
import com.miku.service.CreditService;
import com.miku.service.UserService;
import com.miku.utils.ExcelUtils;
import com.miku.utils.SparkUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.miku.constant.AiConstant.CHAHE_KEY;
import static com.miku.constant.MessageConstant.BI_EXCHANGE;
import static com.miku.constant.MessageConstant.BI_ROUTING_KEY;

@Api(tags = "图表接口")
@RestController
@RequestMapping("/chart")
@Slf4j
public class ChartController {
    @Resource
    private ChartService chartService;

    @Resource
    private UserService userService;
    @Resource
    private SparkUtils sparkUtils;
    @Resource
    private RedisLimterManager redisLimterManager;
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private MessageProducer messageProducer;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private CreditService creditService;


    /**
     * 调用ai服务生成智能生成
     *
     * @param multipartFile
     * @param aiRequest
     * @param request
     * @return
     */
    @ApiOperation(value = "调用ai服务生成智能生成图表和结论")
    @PostMapping("/generate")
    public BaseResponse<BiResponse> genSmartChart(@RequestPart("file") MultipartFile multipartFile,
                                                  GenerByAiRequest aiRequest, HttpServletRequest request) {
        String name = aiRequest.getName();
        String goal = aiRequest.getGoal();
        String chartType = aiRequest.getChartType();
        //校验
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不能为空");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "图表名称不能超过100个字符");
        //校验文件
        String filename = multipartFile.getOriginalFilename();
        long fileSize = multipartFile.getSize();
        final long ONE_MB = 1024 * 1024L;
        //文件大小不能超过10MB
        ThrowUtils.throwIf(fileSize > 10 * ONE_MB, ErrorCode.PARAMS_ERROR, "文件大小不能超过10MB");
        //校验文件后缀
        String suffix = FileUtil.getSuffix(filename);
        final List<String> suffixList = Arrays.asList("xls", "xlsx", "csv", "png", "jpg", "jpeg", "webp");
        ThrowUtils.throwIf(!suffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件格式不正确");
        //TODO:接入其它的审核 可以审核文本内容 ，图表等
        User loginUser = userService.getLoginUser(request);
        //限流判断-每个用户一个限流器
        redisLimterManager.doRateLimit(AiConstant.LIMIT_PREFIX_AI + loginUser.getId().toString());
        //用户输入
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析需求:").append("\n");
        //拼接分析目标
        String userGoal = goal;
        if (StringUtils.isNotBlank(chartType)) {
            userGoal += ",请使用:" + chartType;
        }
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据:").append("\n");
        //压缩Excel数据
        //读取用户上传的excel文件 并且处理
        String csvData = ExcelUtils.excelCsv(multipartFile);
        userInput.append(csvData).append("\n");
        //插入到数据库
        Chart chart = new Chart();
        chart.setName(name);
        chart.setUserId(loginUser.getId());
        chart.setGoal(goal);
        chart.setChartData(csvData);
        chart.setChartType(chartType);
        chart.setExecMessage(AiConstant.TASK_RUNNING);
        boolean save = chartService.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "图表保存失败");
        CompletableFuture.runAsync(() -> {
            //异步执行任务
            //调用ai服务，获取返回结果
            extracted(userInput,3,5000,chart);
        }, threadPoolExecutor);
        BiResponse biResponse = new BiResponse();
        biResponse.setGenChatrId(chart.getId());
        return ResultUtils.success(biResponse);
    }

    /**
     * 调用ai服务生成智能生成
     * 使用消息队列异步处理消息
     * @param multipartFile
     * @param aiRequest
     * @param request
     * @return
     */
    @ApiOperation(value = "异步调用ai服务生成智能生成图表和结论")
    @PostMapping("/generate/mq")
    public BaseResponse<BiResponse> genSmartChartByMq(@RequestPart("file") MultipartFile multipartFile,
                                                  GenerByAiRequest aiRequest, HttpServletRequest request) {
        String name = aiRequest.getName();
        String goal = aiRequest.getGoal();
        String chartType = aiRequest.getChartType();
        //校验
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不能为空");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "图表名称不能超过100个字符");
        //校验文件
        String filename = multipartFile.getOriginalFilename();
        long fileSize = multipartFile.getSize();
        final long ONE_MB = 1024 * 1024L;
        //文件大小不能超过10MB
        ThrowUtils.throwIf(fileSize > 10 * ONE_MB, ErrorCode.PARAMS_ERROR, "文件大小不能超过10MB");
        //校验文件后缀
        String suffix = FileUtil.getSuffix(filename);
        final List<String> suffixList = Arrays.asList("xls", "xlsx", "csv", "png", "jpg", "jpeg", "webp");
        ThrowUtils.throwIf(!suffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件格式不正确");
        //TODO:接入其它的审核 可以审核文本内容 ，图表等
        User loginUser = userService.getLoginUser(request);
        //限流判断-每个用户一个限流器
        redisLimterManager.doRateLimit(AiConstant.LIMIT_PREFIX_AI + loginUser.getId().toString());
        //压缩Excel数据
        //读取用户上传的excel文件 并且处理
        String csvData = ExcelUtils.excelCsv(multipartFile);
        //插入到数据库
        Chart chart = new Chart();
        chart.setName(name);
        chart.setUserId(loginUser.getId());
        chart.setGoal(goal);
        chart.setChartData(csvData);
        chart.setChartType(chartType);
        chart.setExecMessage(AiConstant.TASK_RUNNING);
        boolean save = chartService.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "图表保存失败");

        messageProducer.sendMessage(BI_EXCHANGE,BI_ROUTING_KEY,chart.getId().toString());
        BiResponse biResponse = new BiResponse();
        biResponse.setGenChatrId(chart.getId());
        return ResultUtils.success(biResponse);
    }

    /**
     * 任务执行逻辑
     * @param userInput-
     * @param maxRetries- 最大重试次数
     * @param delayMillis - 延迟重试时间
     */
    private void extracted(StringBuilder userInput, int maxRetries, int delayMillis,Chart chart) {
        boolean success = false;
        int attempts = 0;
        while (!success && attempts < maxRetries) {
            try {
                //将Chart表的状态改为执行中
                boolean update = chartService.update(Wrappers.<Chart>lambdaUpdate()
                        .eq(Chart::getId, chart.getId())
                        .set(Chart::getStatus, ChartStatusEnum.RUNNING.getValue()));
                if (!update) {
                    handleChartError(chart, "更新图表状态为运行中失败");
                    return;
                }
                log.info("任务执行中:{}", Thread.currentThread().getName());

                String result = sparkUtils.synchronousCalls(AiConstant.MATH_MAP, userInput.toString());
                //对返回结果做拆分,按照5个中括号进行拆分
                String[] split = result.split("【【【【【");
                //拆分完还需要校验
                if (split.length < 3) {
                   handleChartError(chart,"ai生成数据格式不正确");
                   return;
                }
                String genChart = split[1].trim();
                String genResult = split[2].trim();
                //执行成功
                //修改数据库状态为执行成功
                boolean update1 = chartService.update(Wrappers.<Chart>lambdaUpdate()
                        .eq(Chart::getId, chart.getId())
                        .set(Chart::getStatus, ChartStatusEnum.SUCCEEd.getValue())
                        .set(Chart::getGenChart,genChart)
                        .set(Chart::getGenResult,genResult)
                        .set(Chart::getExecMessage,"图表生成成功！！！"));
                if (!update1) {
                    handleChartError(chart,"更新图表状态为成功中失败");
                    return;
                }
                //消费积分-每次使用消费1积分
                Boolean credits = creditService.consumeCredits(chart.getUserId(), -1L);
                ThrowUtils.throwIf(!credits,ErrorCode.OPERATION_ERROR,"你的积分不足");
                success = true;
            } catch (BusinessException e) {
                //保持任务中断状态
                Thread.currentThread().interrupt();
                log.info("任务中断:{}", userInput);
            } catch (Exception e) {
                attempts++;
                log.info("任务执行失败,正在第:{}次重试", attempts);
                try {
                    //等待一段时间再重试
                    Thread.sleep(delayMillis);
                } catch (InterruptedException ex) {
                    //保持任务中断状态
                    Thread.currentThread().interrupt();
                }
            }
        }
        if (!success) {
            //把任务标记为执行失败
            handleChartError(chart,"任务执行失败,达到最大重试次数");
            log.info("任务执行失败,达到最大重试次数:{}", userInput);
        }
    }


    /**
     * 修改失败,改数据库的状态为失败
     * @param chart
     */

    private void handleChartError(Chart chart,String execMessage) {
            //修改数据库状态失败
        boolean update = chartService.update(Wrappers.<Chart>lambdaUpdate()
                .eq(Chart::getId, chart.getId())
                .set(Chart::getStatus, ChartStatusEnum.FAILED.getValue())
                .set(Chart::getExecMessage, execMessage));
        if (!update){
            log.error("更新图表状态为失败,失败,chartId:{}",chart.getId());
        }

    }


    /**
     * 校验文件
     *
     * @param multipartFile
     * @param fileUploadBizEnum 业务类型
     */
    private void validFile(MultipartFile multipartFile, FileUploadBizEnum fileUploadBizEnum) {
        // 文件大小
        long fileSize = multipartFile.getSize();
        // 文件后缀
        String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
        final long ONE_M = 1024 * 1024L;
        if (FileUploadBizEnum.USER_AVATAR.equals(fileUploadBizEnum)) {
            if (fileSize > ONE_M) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小不能超过 1M");
            }
            if (!Arrays.asList("jpeg", "jpg", "svg", "png", "webp").contains(fileSuffix)) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型错误");
            }
        }
    }

    /**
     * 添加图表信息
     *
     * @param chartAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("添加图表信息")
    public BaseResponse<Long> addChart(@RequestBody ChartAddRequest chartAddRequest, HttpServletRequest request) {
        if (chartAddRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartAddRequest, chart);
        User loginUser = userService.getLoginUser(request);
        chart.setUserId(loginUser.getId());
        boolean result = chartService.save(chart);
        //加入缓存
        redisTemplate.opsForValue().set(CHAHE_KEY+chart.getId(),JSONUtil.toJsonStr(chart));
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        long newChartId = chart.getId();
        return ResultUtils.success(newChartId);
    }

    /**
     * 删除图表信息
     *
     * @param deleteRequest
     * @param request
     * @return
     */
    @PostMapping("/delete")
    @ApiOperation("删除图表信息")
    public BaseResponse<Boolean> deleteChart(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
        if (deleteRequest == null || deleteRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.getLoginUser(request);
        long id = deleteRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可删除
        if (!oldChart.getUserId().equals(user.getId()) && !userService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        boolean b = chartService.removeById(id);
        //删除缓存
        redisTemplate.delete(CHAHE_KEY + id);
        return ResultUtils.success(b);
    }

    /**
     * 更新图表信息（仅管理员）
     *
     * @param chartUpdateRequest
     * @return
     */
    @PostMapping("/update")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    @ApiOperation("更新图表信息")
    public BaseResponse<Boolean> updateChart(@RequestBody ChartUpdateRequest chartUpdateRequest) {
        if (chartUpdateRequest == null || chartUpdateRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartUpdateRequest, chart);
        long id = chartUpdateRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        boolean result = chartService.updateById(chart);
        //删除缓存
        redisTemplate.delete(CHAHE_KEY + id);
        return ResultUtils.success(result);
    }

    /**
     * 根据 id 获取
     *
     * @param id
     * @return
     */
    @GetMapping("/get")
    @ApiOperation("根据 id 获取图表信息")
    public BaseResponse<Chart> getChartVOById(long id, HttpServletRequest request) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //先查询缓存
        String chartJson = redisTemplate.opsForValue().get(CHAHE_KEY + id);
        //判断缓存是否命中,且是否是空值
        if (chartJson != null && !"".equals(chartJson)) {
            return ResultUtils.success(JSONUtil.toBean(chartJson, Chart.class));
        }
        Chart chart = chartService.getById(id);
        if (chart == null) {
            //缓存空值
            redisTemplate.opsForValue().set(CHAHE_KEY + id, "", 30, TimeUnit.HOURS);
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
        }
        //写入缓存,设置超时时间-30小时
        redisTemplate.opsForValue().set(CHAHE_KEY + id, JSONUtil.toJsonStr(chart), 30, TimeUnit.HOURS);
        return ResultUtils.success(chart);
    }

    /**
     * 分页获取列表（仅管理员）
     *
     * @param chartQueryRequest
     * @return
     */
    @PostMapping("/list/page")
    @ApiOperation("分页获取图表列表")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Page<Chart>> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        Page<Chart> chartPage = chartService.page(new Page<>(current, size),
                chartService.getQueryWrapper(chartQueryRequest));
        return ResultUtils.success(chartPage);
    }


    /**
     * 分页获取当前用户创建的资源列表
     *
     * @param chartQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/my/list/page")
    @ApiOperation("分页获取当前用户创建的图表列表")
    public BaseResponse<Page<Chart>> listMyChartVOByPage(@RequestBody ChartQueryRequest chartQueryRequest,
                                                         HttpServletRequest request) {
        if (chartQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        chartQueryRequest.setUserId(loginUser.getId());
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        Page<Chart> chartPage = chartService.page(new Page<>(current, size),
                chartService.getQueryWrapper(chartQueryRequest));
        return ResultUtils.success(chartPage);
    }



    /**
     * 编辑（用户）
     *
     * @param chartEditRequest
     * @param request
     * @return
     */
    @PostMapping("/edit")
    @ApiOperation("编辑图表")
    public BaseResponse<Boolean> editChart(@RequestBody ChartEditRequest chartEditRequest, HttpServletRequest request) {
        if (chartEditRequest == null || chartEditRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartEditRequest, chart);
        User loginUser = userService.getLoginUser(request);
        long id = chartEditRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可编辑
        if (!oldChart.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        //修改数据库
        boolean result = chartService.updateById(chart);
        //删除缓存
        redisTemplate.delete(CHAHE_KEY + id);
        return ResultUtils.success(result);
    }
}
